/*
    单页面架构:
            把资源复用性提高至最大,最大限度利用ajax,整个前端项目只有一个页面的架构.

            本框架由以下几个机制组成:
                一次加载机制:本框架封装了html加载,js加载,以及css加载等各种文件的加载操作,并且只加载一次
                监听机制:采用观察者模式实现
                回退事件机制:对原生历史事件进行拦截,支持回退操作
                统一页面切换操作:本框架实现了统一的页面切换操作


            本框架基于jQuery实现,使用前首先引入jQuery
 */


//统一的html,js,css文件加载机制

//已经加载过的文件
let hasLoad = {};

//加载css文件
function loadcss(path) {

    if (!hasLoad[path]) {

        $("<link>")
            .attr({
                rel: "stylesheet",
                type: "text/css",
                href: path
            })
            .appendTo("head");
        hasLoad[path] = true;
    }
}

//批量加载css文件
function loadmultiplecss(paths) {
    for (let i in paths) {
        loadcss(paths[i]);
    }
}


/*
    加载js文件
    callback为加载完成以后调用的函数
*/
function loadjs(path, callback) {
    if (!hasLoad[path]) {
        $.getScript(path, callback);
        hasLoad[path] = true;
    } else {
        callback();
    }
}

/*
    加载多个js文件
    callback为加载完所有js文件后调用的函数
*/
function loadmultiplejs(paths, callback) {
    let n = paths.length;
    for (let i in paths) {
        loadjs(paths[i], function () {
            n--;
            if (n === 0) {
                callback();
            }
        });
    }
}


/*
    页面加载函数:
            两个参数:
                    1.id: 容器id,该参数必须以'#作为开头'
                    2.path: 加载的路径,path中如果不含有'?'则认为是静态页面,不会重复加载.如果含有?则认为带有请求参数,页面是动态的,则会重新加载

*/
function loadpage(id, path) {


    if (!id.startsWith('#')) {
        throw  new Error('id必须以#作为开头');
    }

    if (path.includes('?')) {//强制重新加载
        $(id).load(path);
        hasLoad[id] = path;
    }

    else {
        if (path !== hasLoad[id]) { //判断是否已经加载过
            $(id).load(path);
            hasLoad[id] = path;
        }
    }
}


/*
    单页面架构集合
*/

var Sp = {
    switchPage: function (containerId, pageUrl) {
        console.log('使用时请自定义Sp.switchPage函数');
    }
};


/*

    统一页面切换机制

    对使用了page_switcher类的a标签,按钮等有效

    两个自定参数:
        1.page-url:加载的页面
        2.container-id:容器id 必须使用 '#' 作为开头

    最终使用Sp中的switchPage 作为页面切换的实现.推荐自定义该函数

*/

$(document).on('click', '.page_switcher', function (e) {

    e.preventDefault();

    //将指定的页面加载到容器

    let el = $(this);

    let pageUrl = el.attr('page-url');

    let containerId = el.attr('container-id');

    Sp.switchPage(containerId, pageUrl);

});


/*
    事件监听机制.使用观察者模式.

    source:事件源
    listener:监听函数,当检测到相应的事件源后会被调用
    onlyOnce:表明这是一个一次性监听的器,当监听器函数监听到指定的事件时,处理完之后监听器就立即失效
    data:是触发监听事件的参数


    单Sp框架中,推荐使用监听机制进行不同页面之间相互调用

    建议在使用过程中对事件的声明周期进行细粒度的控制

*/


let Event = {
    events: {},

    //注册事件
    on: function (source, listener, onlyOnce) {
        if (!this.events[source]) {
            this.events[source] = [];
        }
        let event = {
            listener: listener,
            onlyOnce: onlyOnce
        };
        this.events[source].push(event);

    },

    //触发
    fanout: function (source, data) {
        if (this.events[source]) {
            let cs = this.events[source];
            let ks = [];

            //逐一将cs中的事件弹出.如果是非onlyOnce则重新放回
            while (cs.length > 0) {
                let pop = cs.pop();
                try {
                    pop.listener(data);
                }
                catch (e) {
                }
                if (!pop.onlyOnce) {
                    ks.push(pop);
                }
            }
            this.events[source] = ks;
        }
    },


    //以下两个是针对展示页面专门封装的函数

    display: function (id, data) {//id为页面id
        this.fanout('__display_' + id, data);
    },
    onDisplay: function (id, listener, onlyOnce) {
        this.on('__display_' + id, listener, onlyOnce);
    }

};


/*
    回退事件机制

    historyBack和doHistoryBack:
            由于回退事件机制的底层是拦截历史事件实现的,所以原生的历史事件就无法得到执行.
            historyBack是原生历史事件执行的准备事件,doHistoryBack则是实际进行历史事件的回退

            一般流程为 : historyBack() -> [用户确认] ->  doHistoryBack() -> 原生历史事件执行


    注意:
        请不要在回退事件中包含其他的回退事件,否则有可能造成无限循环的问题

 */

let historyBackFlag = false;
let Backs = {
    historyBack: function () {
        if (historyBackFlag) {
            window.history.back();
        }
    },
    doHistoryBack: function () {
        historyBackFlag = true;
        window.history.back();
    },
    history: [],
    current: undefined,//当前事件

    /*
        ope:主体函数,在该事件成为当前事件前执行

        compensation:补偿函数,在该事件从当前事件位置移除时执行.可以理解为ope函数的反函数,用来消除ope函数的影响

        pass:该回退事件是否应该被忽略.如果为true,主体事件更不会被执行

        当补偿函数返回true时:
                表明这个补偿函数没有带来有意义的影响(比如该关闭的弹窗早已被手动关闭),此时会执行下一个补偿事件,直至补偿事件有意义


     */
    push: function (ope, compensation, pass) {
        let event = {};
        event.ope = ope;
        if (compensation) {
            event.compensation = compensation;
        }
        if (pass) {
            event.pass = pass;
        }

        //将当前事件压入栈
        this.history.push(this.current);

        //执行操作
        ope();
        this.current = event;
    },
    pop: function () {  //返回true表明返回事件栈未到底

        let pop;

        while (true) {
            pop = this.history.pop();

            if (pop) {
                if (!pop.pass) {

                    if (this.current.compensation) {
                        if (this.current.compensation()) {
                            this.current = pop;
                            if (this.current.pass) {

                                while (true) {
                                    pop = this.history.pop();
                                    if (!pop) {
                                        break;
                                    } else if (!pop.pass) {
                                        this.current = pop;
                                        break;
                                    }

                                    //补偿函数一定会被执行
                                    if (pop.compensation) {
                                        pop.compensation();
                                    }
                                }
                            } else if (this.current.compensation) {//补偿事件一定会被执行
                                this.current.compensation();
                            }

                            //进行下一轮尝试
                            continue;
                        }
                    }

                    this.current = pop;
                    pop.ope();
                    return true;
                } else if (pop.compensation) {//如果事件被跳过,仅仅是主体函数不会执行,但补偿函数依旧会执行
                    pop.compensation();
                }
            }
            else {//已到栈低
                return false;
            }
        }
    },
    init: function (rootEvent) {//rootEvent是事件栈为空时调用事件
        if (rootEvent) {
            this.rootEvent = rootEvent;
        }

        //拦截返回事件进行手动托管
        //手动托管返回事件
        window.history.pushState(null, null, "");
        window.addEventListener("popstate", function (e) {
            //这里会监听事件popstate事件,
            // 而history.back()则会触发popstate事件,
            // 所以导致无限重复.但是如果history.back()在监听器监听到足够短的时间内执行,则不会出现这种情况

            if (!Backs.pop()) {
                Backs.rootEvent();
            }
            window.history.pushState(null, null, "");
        });

    }

};
